当时阅读时候的版本:ClusterFuzz v2.0.1
总览
首先运行bot的入口是python butler.py run_bot
1 | args = parser.parse_args() |
之后执行中的src/local/butler/run_bot.py
中的execute
1 | def execute(args): |
之后就是执行src/python/bot/startup/run_bot.py
,而这里面是有定义main函数的
1 | if __name__ == '__main__': |
上面就是一些初始化就执行main函数,我们来看main函数
1 | def main(): |
看到task_loop()函数,task = tasks.get_task()
获取任务,commands.process_command(task)
执行命令并删除任务
1 | def task_loop(): |
获取任务
我们先看get_task函数,可以看这里除了fuzz的任务,还有其他任务
1 | def get_task(): |
我们还是比较关心fuzz,看get_fuzz_task,这里是获取argument和job
1 | def get_fuzz_task(): |
继续跟fuzzer_selection.get_fuzz_task_payload(),这里是去谷歌云那边查询任务了,最后随机选取任务返回
1 | def get_fuzz_task_payload(platform=None): |
执行任务
1 | # pylint: disable=too-many-nested-blocks |
看run_command,实际是task_module.execute_task(task_argument, job_name)
执行
1 | def run_command(task_name, task_argument, job_name): |
其实一开始判断task_name是否在COMMAND_MAP中,可以看到除了fuzz任务外,还有很多任务
1 | COMMAND_MAP = { |
继续跟task_module.execute_task,我们关注fuzz的吧,就是fuzz_task.execute_task
1 | def execute_task(fuzzer_name, job_type): |
先获取超时,之后初始化FuzzingSession,初始化代码如下:
1 |
|
run的代码
1 | def run(self): |
实在太多了,前面做了一些初始化,之后是选择引擎进行fuzz——self.do_engine_fuzzing(engine_impl)
,没有的就是黑盒测试self.do_blackbox_fuzzing
1 | engine_impl = engine.get(fuzzer.name) |
接下来最后就是处理crashes,上传crash,最后更新任务的状态
1 | # Process and save crashes to datastore. |
do_engine_fuzzing
1 | def do_engine_fuzzing(self, engine_impl): |
实际fuzz是run_engine_fuzzer
1 | def run_engine_fuzzer(engine_impl, target_name, sync_corpus_directory, |
引擎类
上面提到的运行fuzzer,是通过engine.get是获取引擎类,get函数如下
1 | def get(name): |
而之前得先注册
1 | def register(name, engine_class): |
而注册这个在src/python/bot/startup/run_bot.py
的时候注册的
1 | ...... |
跟过去可以看注册了libFuzzer,honggfuzz和syzkaller,有疑问的是咋没有afl
1 | def run(): |
do_blackbox_fuzzing
1 | def do_blackbox_fuzzing(self, fuzzer, fuzzer_directory, job_type): |
在self.generate_blackbox_testcases里面是会实际启动fuzzer的,注释说的是Run the blackbox fuzzer and generate testcases.
1 | def generate_blackbox_testcases(self, fuzzer, fuzzer_directory, |
下面是builtin_fuzzer = builtin_fuzzers.get(fuzzer.name)
所能获取到的
1 | BUILTIN_FUZZERS = { |
下面的do_blackbox_fuzzing函数的后半部分,是处理testcases的
1 | ............ |
上面调用了run_testcase_and_return_result_in_queue
,它是运行一个testcases,并且上传crash信息了
1 | def run_testcase_and_return_result_in_queue(crash_queue, |
里面又调用了_do_run_testcase_and_return_result_in_queue
,里面就是上传CrashResult了,根据这,实际的fuzz代码应该就是self.generate_blackbox_testcases
1 | def _do_run_testcase_and_return_result_in_queue(crash_queue, |
如何获取要运行的target
我们上传zip包,但是里面可能有多个target,有些可能只是fuzzer所需文件,那怎么找到要运行的target呢
我们跟踪一下
是fuzz task,就进来src/python/bot/tasks/fuzz_task.py
执行execute_task
1 | def execute_task(fuzzer_name, job_type): |
上面调用FuzzingSession
类里面的run函数,在run函数里面https://github.com/google/clusterfuzz/blob/9c2065a7f7b7802936b1133733402adc65ac0c4c/src/python/bot/tasks/fuzz_task.py#L1843
这里调用了build_manager.setup_build
1 | build_setup_result = build_manager.setup_build( |
跟进build_manager.setup_build
1 | def setup_build(revision=0, target_weights=None): |
setup_build 中的setup_custom_binary
首先是setup_custom_binary
,里面调用了CustomBuild的setup
1 | build = CustomBuild( |
看setup
1 | def setup(self): |
上面的_set_random_fuzz_target_for_fuzzing_if_needed
就选择压缩包中的二进制文件了
1 | def _set_random_fuzz_target_for_fuzzing_if_needed(fuzz_targets, target_weights): |
但我们初步选择的是_get_fuzz_targets_from_dir
,之后根据这个结果,在通过target_weights从里面选,所以第一部选还是在_get_fuzz_targets_from_dir
,确保他是一个fuzzer
CustomBuild没有这个函数,那实际就是父类的_get_fuzz_targets_from_dir
,可以看到是调用get_fuzz_targets
1 | def _get_fuzz_targets_from_dir(self, build_dir): |
而这个fuzzer_utils.get_fuzz_targets
是 from bot.fuzzers import utils as fuzzer_utils
,那就是src/python/bot/fuzzers/utils.py
里面的
1 | def get_fuzz_targets(path): |
继续跟进get_fuzz_targets_local
1 | def get_fuzz_targets_local(path): |
就是is_fuzz_target_local
了
1、首先得名字得满足正则VALID_TARGET_NAME
2、后缀名是ALLOWED_FUZZ_TARGET_EXTENSIONS
,即无后缀,exe或者par
3、最后就是文件中得有FUZZ_TARGET_SEARCH_BYTES
,也即LLVMFuzzerTestOneInput
这个函数
1 | ALLOWED_FUZZ_TARGET_EXTENSIONS = ['', '.exe', '.par'] |